package com.micro.platform.oauth.framework.defaults;

import com.micro.platform.oauth.framework.config.properties.OauthProperties;
import com.micro.platform.oauth.framework.domain.*;
import com.micro.platform.oauth.framework.exception.OauthException;
import com.micro.platform.oauth.framework.exception.OauthExceptionEnum;
import com.micro.platform.oauth.framework.interfaces.*;
import com.micro.platform.oauth.framework.store.jwt.JwtTemplate;
import com.micro.platform.oauth.framework.store.jwt.JwtTokenStore;
import com.micro.platform.oauth.framework.store.redis.RedisTokenStore;
import com.micro.platform.starter.utils.StringUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.util.Arrays;
import java.util.Map;

public class DefaultTokenService implements ITokenService {
    private ITokenStore tokenStore;
    private ITokenConvert tokenConvert;
    private OauthProperties oauthProperties;
    private ITokenSerialize tokenSerialize;
    private ISubjectHandler subjectHandler;
    public DefaultTokenService(OauthProperties oauthProperties) {
        this.oauthProperties = oauthProperties;
    }

    @Override
    public boolean isTokenExpired(String token) {
        return getTokenStore().isExpired(token);
    }

    @Override
    public boolean isTokenExpired(AccessToken accessToken) {
        return isTokenExpired(accessToken.getAccessToken());
    }

    @Override
    public boolean validateToken(String token) {
        return getTokenStore().validateToken(token);
    }

    @Override
    public boolean validateToken(AccessToken accessToken) {
        return validateToken(accessToken.getAccessToken());
    }

    @Override
    public RefreshToken getRefreshToken(String refreshToken) {
        return getTokenStore().getRefreshToken(refreshToken);
    }

    @Override
    public AccessToken getAccessToken(String token) {
        return getTokenStore().getAccessToken(token);
    }

    @Override
    public AccessToken createdToken(OauthContext context) {
        String subjectKey = getSubjectHandler().getSubjectKey(context);
        context.setSubjectKey(subjectKey);
        Subject subject = getTokenStore().getSubject(subjectKey);
        if(subject != null){
            AccessToken accessToken = getSubjectHandler().createdToken(subject, context);
            if(accessToken != null && accessToken.unExpires()){
                return accessToken;
            }
        }
        return createAccessToken(subjectKey, context);
    }

    @Override
    public AccessToken createAccessToken(String subjectKey, OauthContext context) {
        AuthorizationDetails authorizationDetails = getTokenConvert().convert(context);
        String[] grantTypes = context.getClientDetails().getAuthorizedGrantTypes();
        String refreshTokenStr = serializeRefreshToken(authorizationDetails.getRefreshTokenData());
        String accessTokenStr = serializeAccessToken(authorizationDetails.getAccessTokenData());
        RefreshToken refreshToken = null;
        if(ArrayUtils.isNotEmpty(grantTypes) && Arrays.stream(grantTypes).anyMatch(grantType -> "refresh_token".equals(grantType))){
            refreshToken = new RefreshToken(refreshTokenStr, authorizationDetails.getRefreshTokenData());
            if(!getTokenStore().saveRefreshToken(refreshToken)){
                throw new OauthException(OauthExceptionEnum.RefreshTokenCreateFiled);
            }
        }
        AccessToken accessToken = new AccessToken(accessTokenStr, refreshToken, authorizationDetails.getAccessTokenData());

        if(!getTokenStore().saveAccessToken(accessToken)){
            throw new OauthException(OauthExceptionEnum.AccessTokenCreateFiled);
        }
        clearSubject(subjectKey);
        bindSubjectKey(subjectKey, accessToken);
        return accessToken;
    }

    private void clearSubject(String subjectKey) {
        Subject subject = getTokenStore().removeSubject(subjectKey);
        if(subject != null){
            String accessToken = subject.getAccessToken();
            String refreshToken = subject.getRefreshToken();
            if(StringUtils.isNotEmpty(accessToken)){
                getTokenStore().removeToken(accessToken);
            }
            if(StringUtils.isNotEmpty(refreshToken)){
                getTokenStore().removeRefreshToken(refreshToken);
            }
        }
    }

    private void bindSubjectKey(String subjectKey, AccessToken accessToken) {
        Subject subject = new Subject();
        subject.setSubjectKey(subjectKey);
        subject.setAccessToken(accessToken.getAccessToken());
        subject.setRefreshToken(accessToken.getRefreshToken());
        subject.setExpiresTime(accessToken.getExpiresTime());
        getSubjectHandler().buildSubject(accessToken, subject);
        if(!tokenStore.saveSubject(subject)){
            throw new OauthException(OauthExceptionEnum.RefreshTokenCreateFiled);
        }
    }

    private String serializeRefreshToken(Map<String, Object> refreshTokenData) {
        return getTokenSerialize().serializeRefreshToken(refreshTokenData);
    }

    private String serializeAccessToken(Map<String, Object> accessTokenData) {
        return tokenSerialize.serialize(accessTokenData);
    }

    @Override
    public boolean removeToken(String token) {
        AccessToken accessToken = getTokenStore().getAccessToken(token);
        if(accessToken != null){
            String subjectKey = accessToken.getSubjectKey();
            this.clearSubject(subjectKey);
        }
        return Boolean.TRUE;
    }

    @Override
    public boolean removeToken(AccessToken accessToken) {
        return removeToken(accessToken.getAccessToken());
    }

    @Override
    public String createdAuthorizationCode(AuthorizationCodeDetails authorizationCodeDetails) {
        return getTokenStore().createdAuthorizationCode(authorizationCodeDetails, this.oauthProperties.getAuthorizationCodeValidityTime());
    }

    @Override
    public AuthorizationCodeDetails validateAuthorizationCode(String authorizationCode) {
        return getTokenStore().validateAuthorizationCode(authorizationCode);
    }

    private ITokenConvert getTokenConvert() {
        if(this.tokenConvert == null){
            this.tokenConvert = new DefaultTokenConvert();
            this.tokenConvert.registerAccessTokenConvert(new DefaultAccessTokenConvert());
            this.tokenConvert.registerRefreshTokenConvert(new DefaultRefreshTokenConvert());
        }
        return this.tokenConvert;
    }

    private ISubjectHandler getSubjectHandler() {
        if(subjectHandler == null){
            subjectHandler = new DefaultSubjectHandler(this.getTokenStore());
        }
        return subjectHandler;
    }

    @Override
    public void setSubjectHandler(ISubjectHandler subjectHandler) {
        this.subjectHandler = subjectHandler;
    }

    private ITokenSerialize getTokenSerialize() {
        if(tokenSerialize == null){
            tokenSerialize = new DefaultTokenSerialize(this.getTokenStore());
        }
        return tokenSerialize;
    }

    @Override
    public void setTokenSerialize(ITokenSerialize tokenSerialize) {
        this.tokenSerialize = tokenSerialize;
    }

    @Override
    public ITokenStore getTokenStore() {
        if(this.tokenStore == null){
            if(OauthProperties.ModeLEnum.MODEL_REDIS.getKey().equals(this.oauthProperties.getModel())){
                this.tokenStore = new RedisTokenStore(this.oauthProperties.getRedis());
            }else {
                JwtTemplate jwtTemplate = new JwtTemplate(this.oauthProperties.getJwt());
                this.tokenStore = new JwtTokenStore(jwtTemplate);
            }
        }
        return this.tokenStore;
    }

    @Override
    public void setTokenStore(ITokenStore tokenStore) {
        this.tokenStore = tokenStore;
    }

    @Override
    public boolean registerAccessTokenConvert(ITokenConvert tokenConvert) {
        return getTokenConvert().registerAccessTokenConvert(tokenConvert);
    }

    @Override
    public boolean registerRefreshTokenConvert(ITokenConvert tokenConvert) {
        return getTokenConvert().registerRefreshTokenConvert(tokenConvert);
    }
}
